Skip to content

feat(knowledge): add comprehensive extension system for KB permissions and external integrations#1025

Open
cocowh wants to merge 4 commits intowecode-ai:mainfrom
cocowh:weagent/kb-permission-extension
Open

feat(knowledge): add comprehensive extension system for KB permissions and external integrations#1025
cocowh wants to merge 4 commits intowecode-ai:mainfrom
cocowh:weagent/kb-permission-extension

Conversation

@cocowh
Copy link
Copy Markdown
Contributor

@cocowh cocowh commented Apr 23, 2026

Summary

This PR introduces a comprehensive extension system for the knowledge base module, enabling external packages to integrate custom permissions, entity bindings, UI components, and lifecycle hooks without modifying the open-source codebase.

Backend — Permission Resolver Extension

  • Add IKbPermissionResolver interface and DefaultKbPermissionResolver no-op base in backend/app/services/readers/kb_permissions.py, following the existing groups / group_members reader pattern
  • get_user_kb_permission() calls the resolver as the final fallback after all built-in checks
  • list_knowledge_bases() (ALL scope) calls get_accessible_kb_ids() to include externally-granted KBs
  • Thread-safe lazy singleton via double-checked locking
  • Defensive try/except guards at both call sites so a faulty extension cannot break core functionality

Backend — Python Entry Points

  • Use Python entry points (wegent.kb_permissions) for dynamic resolver loading instead of SERVICE_EXTENSION env var
  • _load_from_entry_points() discovers and loads resolvers from installed packages
  • Python < 3.10 compatibility for entry_points() API
[project.entry-points."wegent.kb_permissions"]
my_resolver = "myext.kb_permissions:MyResolver"

Frontend — Component Registry

  • Generic component registry in registry.ts to override DocumentPanel and KnowledgeDetailPanel at runtime
  • registerComponents() / getComponent() / hasComponent() / clearRegistry()
  • Lazy resolution via useMemo at render time to avoid module-load ordering issues

Frontend — External Binding API

  • BindingProvider / BindingProviderRegistry: Plugin-based architecture for external systems (ERP, OA, CRM)
  • ExternalBindingApi: Unified CRUD operations (search, list, add, remove, sync)
  • setExternalBindingApi() / getExternalBindingApi() / hasExternalBindingApi() pattern
  • Comprehensive types: BindableItem, BindingSearchResult, BindableTypeConfig, ExternalBinding

Frontend — Permission Extension Tabs

  • ExtensionTabConfig interface in KbPermissionsPanel with id, label, icon, component, and requiresManagePermission
  • extensionTabs prop on KbPermissionsPanel and permissionExtensionTabs on KnowledgeDetailPanel

Frontend — Create KB Dialog Extension

  • KnowledgeBaseFormSections: Well-defined slot positions (afterDescription, afterAdvanced)
  • setCreateKbFormSections() / getCreateKbFormSections() for form section injection
  • setPostCreateHandler() / runPostCreateHandler() for post-creation async hooks
  • Fully wired on both Desktop and Mobile pages

Frontend — Role Select Extension

  • setRoleSelectComponent() / getRoleSelectComponent() for replacing the AddUserForm role dropdown
  • Enables ERP integrations to show custom role options alongside standard ones

Documentation

  • Rewritten docs/en/developer-guide/kb-permission-extension.md and docs/zh/developer-guide/kb-permission-extension.md
  • Covers all extension points with code examples, best practices, and a summary table

Motivation

Internal deployments often need to extend the knowledge base system with:

  • Department-level permissions from external identity systems
  • Entity bindings to ERP/OA/CRM systems
  • Custom UI sections in KB creation dialogs
  • Post-creation workflows (e.g., auto-binding external entities)

This PR provides all necessary extension points while keeping service-specific code completely isolated in external packages.

Files Changed

Area Files Description
Backend 4 new + 2 modified Permission resolver interface, entry points, lazy loader, tests
Frontend 14 new/modified Registry, binding API, permission tabs, form slots, state bridges
Docs 2 files Complete English and Chinese documentation
i18n 2 files Missing translation key document.permission.personal

Commits

This branch has been rebased into 2 logical commits:

  1. feat(backend) — Permission resolver interface, default implementation, lazy singleton, and tests
  2. feat(frontend) — All frontend extension points, backend entry points, and documentation

Test Plan

Backend

  • DefaultKbPermissionResolver.resolve always returns None
  • DefaultKbPermissionResolver.get_accessible_kb_ids always returns []
  • Entry point loading works correctly with valid resolvers
  • ImportError logs a warning and falls back to default
  • General exception logs a warning and falls back to default
  • _LazyReader initialises only once (lazy singleton)
  • Creator check short-circuits before external resolver
  • All 128 existing KB permission tests pass without regression

Frontend

  • Component registry resolves at render time (not module load time)
  • Register/unregister/has/clear work correctly
  • External binding API provider registration and lookup
  • Form sections render at correct slot positions
  • Post-creation hook fires after KB creation
  • Custom role select component renders instead of default dropdown

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a pluggable KB permission resolver (lazy-loaded proxy and default impl), integrates it into KB listing and per-user permission checks as a final fallback, updates package exports, and adds tests covering resolver loading and integration.

Changes

Cohort / File(s) Summary
KB Permission Resolver Implementation
backend/app/services/readers/kb_permissions.py
New IKbPermissionResolver interface, DefaultKbPermissionResolver, dynamic _create_resolver() loading via settings.SERVICE_EXTENSION, and a thread-safe lazy proxy exported as kb_permission_resolver. Falls back and logs on import/wrap errors.
Reader Package Export
backend/app/services/readers/__init__.py
Exports kb_permission_resolver in __all__ and updates package usage examples.
Knowledge Service Integration
backend/app/services/knowledge/knowledge_service.py
list_knowledge_bases(..., scope=ResourceScope.ALL) calls kb_permission_resolver.get_accessible_kb_ids(db, user_id) (guarded by try/except with warning) and includes returned external KB IDs in the DB query OR-filter when non-empty.
Share Service Integration
backend/app/services/share/knowledge_share_service.py
KnowledgeShareService.get_user_kb_permission now calls kb_permission_resolver.resolve(db, knowledge_base_id, user_id, kb) as the final fallback; a non-None role authorizes the user (is_creator=False). Docstring updated.
Tests
backend/tests/services/readers/test_kb_permission_resolver.py
New tests for default resolver behavior, extension loading success/failure, _LazyReader lazy-init and delegation, and integration tests ensuring resolver is invoked only as last fallback and skipped for KB creators.

Sequence Diagrams

sequenceDiagram
    actor User
    participant KBS as KnowledgeService
    participant DB as Database
    participant KPR as kb_permission_resolver

    User->>KBS: list_knowledge_bases(ResourceScope.ALL)
    KBS->>DB: Query built-in KB IDs (personal/team/org/shared/bound)
    DB-->>KBS: Built-in KB IDs
    KBS->>KPR: get_accessible_kb_ids(db, user_id)
    KPR-->>KBS: External KB IDs (possibly [])
    KBS->>DB: Apply combined filters including external IDs
    DB-->>KBS: Consolidated KB list
    KBS-->>User: Return filtered KBs
Loading
sequenceDiagram
    actor User
    participant KSS as KnowledgeShareService
    participant DB as Database
    participant KPR as kb_permission_resolver

    User->>KSS: get_user_kb_permission(kb_id, user_id)
    KSS->>DB: Check if user is creator
    alt Creator
        DB-->>KSS: Creator -> ADMIN
        KSS-->>User: Return ADMIN
    else Not Creator
        KSS->>DB: Check explicit ResourceMember
        alt Explicit member found
            DB-->>KSS: Member role
            KSS-->>User: Return role
        else No explicit member
            KSS->>DB: Check group/task bindings
            alt Group/task binding grants role
                DB-->>KSS: Role
                KSS-->>User: Return role
            else No built-in grant
                KSS->>KPR: resolve(db, kb_id, user_id, kb)
                alt External resolver returns role
                    KPR-->>KSS: Role string
                    KSS-->>User: Authorize with resolved role
                else External resolver returns None
                    KSS-->>User: Access Denied
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code and left a trace,
A lazy resolver now finds its place,
External whispers peek and cheer,
KB doors open — then disappear,
Tests and logs hold carrots near.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(knowledge): add comprehensive extension system for KB permissions and external integrations' accurately reflects the main changes: introducing an extension system for KB permissions with IKbPermissionResolver interface, DefaultKbPermissionResolver, and integration into knowledge base permission checks and listing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/tests/services/readers/test_kb_permission_resolver.py (1)

268-275: Dead assignment: return_value is immediately overridden by side_effect.

Line 268 sets first.return_value = mock_kb, but line 270 then sets first.side_effect = [mock_kb, None]. Since side_effect takes precedence over return_value, line 268 has no effect and can be misleading to readers. Consider removing it.

♻️ Suggested fix
-        # Simulate KB query returning mock_kb
-        mock_query.return_value.filter.return_value.first.return_value = mock_kb
-        # Simulate no explicit permission
-        mock_query.return_value.filter.return_value.first.side_effect = [
+        # KB lookup returns mock_kb, subsequent explicit-permission lookup returns None
+        mock_query.return_value.filter.return_value.first.side_effect = [
             mock_kb,   # KB lookup
             None,      # no explicit ResourceMember
         ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/services/readers/test_kb_permission_resolver.py` around lines
268 - 275, Remove the redundant assignment to
mock_query.return_value.filter.return_value.first.return_value (the line that
sets it to mock_kb) because you immediately override behavior by setting
mock_query.return_value.filter.return_value.first.side_effect = [mock_kb, None];
keep only the side_effect assignment so first() returns mock_kb then None, and
update any test comments to reflect that ResourceMember lookup is simulated via
the side_effect; locate these in the tests in test_kb_permission_resolver.py
around the mock_query and mock_resolver setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/services/readers/test_kb_permission_resolver.py`:
- Around line 319-321: The test unpacks three values from
service.get_user_kb_permission into has_access, role, is_creator but never uses
role, causing a Ruff RUF059 warning; change the unpack to use _role (e.g.,
has_access, _role, is_creator) so the unused variable is clearly marked and the
lint warning is silenced—update the unpack in test_kb_permission_resolver where
get_user_kb_permission is called.
- Around line 77-91: The test currently patches kb_permissions._create_resolver
itself, making it tautological; instead remove the patch.object(...) line and
call the real _create_resolver from the imported module to exercise the
implementation: set mock_settings.SERVICE_EXTENSION = "" (as already done),
re-import app.services.readers.kb_permissions, then call
kb_permissions._create_resolver() and assert isinstance(result,
kb_permissions.DefaultKbPermissionResolver) so the test verifies the
no-extension path of _create_resolver rather than a mocked return.

---

Nitpick comments:
In `@backend/tests/services/readers/test_kb_permission_resolver.py`:
- Around line 268-275: Remove the redundant assignment to
mock_query.return_value.filter.return_value.first.return_value (the line that
sets it to mock_kb) because you immediately override behavior by setting
mock_query.return_value.filter.return_value.first.side_effect = [mock_kb, None];
keep only the side_effect assignment so first() returns mock_kb then None, and
update any test comments to reflect that ResourceMember lookup is simulated via
the side_effect; locate these in the tests in test_kb_permission_resolver.py
around the mock_query and mock_resolver setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1269a40b-01d9-4a32-8c88-ab51964ae0ee

📥 Commits

Reviewing files that changed from the base of the PR and between b1b1d20 and ca26aa9.

📒 Files selected for processing (6)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/__init__.py
  • backend/app/services/readers/kb_permissions.py
  • backend/app/services/share/knowledge_share_service.py
  • backend/tests/services/readers/__init__.py
  • backend/tests/services/readers/test_kb_permission_resolver.py

Comment thread backend/tests/services/readers/test_kb_permission_resolver.py Outdated
Comment thread backend/tests/services/readers/test_kb_permission_resolver.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

134-148: Silent fallback when wrap() returns a falsy value.

If an extension's wrap(base) returns None (or any falsy value), the loader silently drops it and returns base without any log, making misconfigured extensions hard to diagnose. Either log a warning or use an explicit is None check.

♻️ Suggested tweak
-        ext = importlib.import_module(f"{settings.SERVICE_EXTENSION}.kb_permissions")
-        result = ext.wrap(base)
-        if result:
-            logger.info("KB permission resolver extension loaded")
-            return result
+        ext = importlib.import_module(f"{settings.SERVICE_EXTENSION}.kb_permissions")
+        result = ext.wrap(base)
+        if result is None:
+            logger.warning(
+                "kb_permissions extension wrap() returned None; using default resolver"
+            )
+        else:
+            logger.info("KB permission resolver extension loaded")
+            return result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/readers/kb_permissions.py` around lines 134 - 148, The
loader currently treats any falsy return from ext.wrap(base) as a success and
silently falls back to base; update the logic in the kb_permissions
extension-loading block to detect falsy returns from ext.wrap(base) (preferably
checking "is None" if None is the intended sentinel) and log a warning
(including the extension name via settings.SERVICE_EXTENSION) when ext.wrap
returns None or another unexpected falsy value, before returning base; keep the
existing ImportError/Exception handlers and only consider the ext.wrap return
value for this additional diagnostic logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 423-427: Wrap the external extension call to
kb_permission_resolver.get_accessible_kb_ids in list_knowledge_bases (in
knowledge_service.py) in a try/except so any runtime exception from the
extension is caught, logged via the module logger (include the exception
details), and ext_kb_ids is set to an empty list to allow built-in KB listing to
continue; apply the same defensive pattern around kb_permission_resolver.resolve
calls in knowledge_share_service.py so resolver exceptions degrade gracefully
instead of propagating.

---

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 134-148: The loader currently treats any falsy return from
ext.wrap(base) as a success and silently falls back to base; update the logic in
the kb_permissions extension-loading block to detect falsy returns from
ext.wrap(base) (preferably checking "is None" if None is the intended sentinel)
and log a warning (including the extension name via settings.SERVICE_EXTENSION)
when ext.wrap returns None or another unexpected falsy value, before returning
base; keep the existing ImportError/Exception handlers and only consider the
ext.wrap return value for this additional diagnostic logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0884b28-d214-453a-9161-e96654e8f580

📥 Commits

Reviewing files that changed from the base of the PR and between ca26aa9 and 7cdfc82.

📒 Files selected for processing (5)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/__init__.py
  • backend/app/services/readers/kb_permissions.py
  • backend/app/services/share/knowledge_share_service.py
  • backend/tests/services/readers/test_kb_permission_resolver.py
✅ Files skipped from review due to trivial changes (1)
  • backend/app/services/readers/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/services/share/knowledge_share_service.py

Comment thread backend/app/services/knowledge/knowledge_service.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

161-172: Lazy singleton initialization is not thread-safe.

_get() performs a check-then-set on self._instance without a lock. Under concurrent first access (e.g., two request threads hitting list_knowledge_bases and get_user_kb_permission simultaneously at startup), _create_resolver() can run more than once — which also re-imports the extension module and re-logs the "extension loaded" message. In practice importlib.import_module is cached so the extra cost is small, but it's worth a threading.Lock (double-checked locking) or eager initialization at import time to make the "called only once" guarantee that the tests assert actually hold in production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/readers/kb_permissions.py` around lines 161 - 172, The
lazy singleton _LazyReader is not thread-safe: _get performs a check-then-set on
_instance without synchronization, so concurrent first accesses can call
_create_resolver() multiple times; fix by adding a threading.Lock as a
class/member (e.g., _init_lock) and implement double-checked locking inside _get
(check _instance, acquire lock, check again, call _create_resolver and assign to
_instance) so only one resolver is created, or alternatively perform eager
initialization by calling _create_resolver() at module import and initializing
_instance once; ensure references in __getattr__ still call _get() and preserve
existing symbols (_LazyReader, _get, _instance, _create_resolver, __getattr__).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 428-435: The except block in list_knowledge_bases references an
undefined logger causing a NameError if
kb_permission_resolver.get_accessible_kb_ids(db, user_id) raises; add a
module-level logger so the defensive handler works: import logging at the top
and define logger = logging.getLogger(__name__) (so the except can call
logger.warning), or alternatively replace the except's logger usage with a local
logging.getLogger(__name__).warning call; reference:
kb_permission_resolver.get_accessible_kb_ids and the except in
list_knowledge_bases.

---

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 161-172: The lazy singleton _LazyReader is not thread-safe: _get
performs a check-then-set on _instance without synchronization, so concurrent
first accesses can call _create_resolver() multiple times; fix by adding a
threading.Lock as a class/member (e.g., _init_lock) and implement double-checked
locking inside _get (check _instance, acquire lock, check again, call
_create_resolver and assign to _instance) so only one resolver is created, or
alternatively perform eager initialization by calling _create_resolver() at
module import and initializing _instance once; ensure references in __getattr__
still call _get() and preserve existing symbols (_LazyReader, _get, _instance,
_create_resolver, __getattr__).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23cf7564-f9f8-4149-84e4-4f23f2aa9ce5

📥 Commits

Reviewing files that changed from the base of the PR and between 7cdfc82 and dadae3b.

📒 Files selected for processing (3)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/kb_permissions.py
  • backend/tests/services/readers/test_kb_permission_resolver.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/services/readers/test_kb_permission_resolver.py

Comment thread backend/app/services/knowledge/knowledge_service.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

135-154: Consider narrowing the fallback error log to include exc_info.

except Exception as e correctly catches the miscellaneous failure modes (missing wrap, wrap raising, etc.), but the warning at line 152 only serializes str(e), which often loses the traceback for non-ImportError failures originating inside the extension. For an extension-loading seam that is otherwise silent, passing exc_info=True on that branch makes diagnosing misbehaving extensions considerably easier without changing behavior.

🛠️ Suggested tweak
-    except Exception as e:
-        logger.warning(f"Failed to load kb_permissions extension: {e}")
+    except Exception as e:  # noqa: BLE001 - extension isolation boundary
+        logger.warning(
+            "Failed to load kb_permissions extension: %s", e, exc_info=True
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/readers/kb_permissions.py` around lines 135 - 154, The
catch-all exception handler that logs "Failed to load kb_permissions extension:
{e}" should include the full traceback for easier debugging; in the except
Exception block that handles failures from importing or calling
settings.SERVICE_EXTENSION.kb_permissions.wrap, change the logger.warning call
that currently serializes only str(e) to pass exc_info=True (keeping the same
message) so the logger records the exception stack trace when wrap() or module
import fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 135-154: The catch-all exception handler that logs "Failed to load
kb_permissions extension: {e}" should include the full traceback for easier
debugging; in the except Exception block that handles failures from importing or
calling settings.SERVICE_EXTENSION.kb_permissions.wrap, change the
logger.warning call that currently serializes only str(e) to pass exc_info=True
(keeping the same message) so the logger records the exception stack trace when
wrap() or module import fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5705820f-989c-4623-856b-1fef5456037f

📥 Commits

Reviewing files that changed from the base of the PR and between dadae3b and 35b3961.

📒 Files selected for processing (2)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/kb_permissions.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/app/services/knowledge/knowledge_service.py (1)

430-437: Silence Ruff BLE001 with an explanatory noqa.

The broad except Exception here is intentional — the whole point is to prevent a faulty extension from taking down the core listing path — but Ruff will keep flagging it (BLE001). Consider adding a # noqa: BLE001 with a short rationale so the intent is explicit and the lint stays clean.

✏️ Suggested diff
             try:
                 ext_kb_ids = kb_permission_resolver.get_accessible_kb_ids(db, user_id)
-            except Exception as e:
+            except Exception as e:  # noqa: BLE001 - never let extension break core listing
                 logger.warning(
                     f"kb_permissions extension get_accessible_kb_ids failed: {e}; "
                     "falling back to empty list"
                 )
                 ext_kb_ids = []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/knowledge/knowledge_service.py` around lines 430 - 437,
Add a targeted `# noqa: BLE001` to the broad except block that wraps the call to
kb_permission_resolver.get_accessible_kb_ids so Ruff stops flagging the
intentional catch; update the except line in the try/except that assigns
ext_kb_ids (the block that logs via logger.warning and falls back to ext_kb_ids
= []) to include `# noqa: BLE001` and a short inline rationale (e.g.,
"intentional broad except to isolate faulty extension"), keeping the rest of the
handler (logger.warning and fallback to empty list) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 430-437: Add a targeted `# noqa: BLE001` to the broad except block
that wraps the call to kb_permission_resolver.get_accessible_kb_ids so Ruff
stops flagging the intentional catch; update the except line in the try/except
that assigns ext_kb_ids (the block that logs via logger.warning and falls back
to ext_kb_ids = []) to include `# noqa: BLE001` and a short inline rationale
(e.g., "intentional broad except to isolate faulty extension"), keeping the rest
of the handler (logger.warning and fallback to empty list) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80c0af4b-55d9-408d-9fc0-134d8959eb0d

📥 Commits

Reviewing files that changed from the base of the PR and between 35b3961 and 29a9fa6.

📒 Files selected for processing (1)
  • backend/app/services/knowledge/knowledge_service.py

@cocowh cocowh force-pushed the weagent/kb-permission-extension branch from 29a9fa6 to 2db0887 Compare April 23, 2026 20:19
cocowh added a commit to cocowh/Wegent that referenced this pull request Apr 28, 2026
…sion systems

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
cocowh added a commit to cocowh/Wegent that referenced this pull request Apr 28, 2026
…sion systems

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
cocowh added a commit to cocowh/Wegent that referenced this pull request Apr 28, 2026
…sion systems (#27)

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
cocowh added 2 commits April 30, 2026 07:22
Introduce IKbPermissionResolver interface and DefaultKbPermissionResolver
no-op base in backend/app/services/readers/kb_permissions.py, following
the existing groups/group_members reader pattern (ext.wrap(base)).

- get_user_kb_permission() calls kb_permission_resolver as the final
  fallback after all built-in checks — no behaviour change when
  SERVICE_EXTENSION is unset (default)
- list_knowledge_bases() (ALL scope) calls
  kb_permission_resolver.get_accessible_kb_ids() to include
  externally-granted KBs in list responses
- _LazyReader uses double-checked locking (threading.Lock) to guarantee
  the singleton is initialised exactly once under concurrent access
- Defensive try/except guards both call sites so a faulty extension
  cannot break core listing or permission checks
- Module-level logger added to knowledge_service.py; kb_permission_resolver
  imported at module level (no circular dependency)
- Full test suite: default resolver, extension loading, ImportError/exception
  fallback, lazy singleton, and integration with get_user_kb_permission

fix(backend): add exc_info to extension loading error log

- Add exc_info=True to exception logging in _create_resolver()
- Add noqa: BLE001 comment to document intentional broad exception catch
- Improves debugging capability when extension loading fails
Add complete frontend extension system for knowledge base module,
along with backend Python entry points for permission resolvers.

Frontend extension points:
- KbPermissionsPanel with ExtensionTabConfig for custom permission tabs
- Component registry (registerComponents/getComponent) for overriding
  DocumentPanel and KnowledgeDetailPanel at render time
- External Binding API (ExternalBindingApi + BindingProviderRegistry)
  for integrating external entity bindings (ERP/OA/CRM)
- Form section injection slots (afterDescription, afterAdvanced)
  in KnowledgeBaseForm for KB creation/edit dialogs
- Post-creation lifecycle hooks via setPostCreateHandler/runPostCreateHandler
- Custom role select component bridge for AddUserForm
- Lazy component resolution pattern to fix registry timing issues

Backend integration:
- Python entry points (wegent.kb_permissions) for dynamic permission
  resolver loading

Documentation:
- Complete English and Chinese docs covering all extension points
  with code examples, best practices, and extension points summary
@cocowh cocowh force-pushed the weagent/kb-permission-extension branch from c30abfd to bf9313a Compare April 29, 2026 23:23
@cocowh cocowh changed the title feat(backend): add external KB permission resolver extension point feat(knowledge): add comprehensive extension system for KB permissions and external integrations Apr 29, 2026
cocowh added 2 commits April 30, 2026 10:13
…sPanel

The ActiveComponent variable was defined via useMemo but never used in the
component. The single-tab and multi-tab rendering paths both use visibleTabs
directly. Removing the dead code to fix ESLint unused-vars error that was
blocking CI checks (lint, E2E compilation).
… DingTalk example

- Remove invalid "See Also" references to non-existent documents
- Add complete backend extension introduction (IKbPermissionResolver interface,
  entry points loading, decorator pattern, permission resolution chain)
- Add end-to-end DingTalk organization integration example covering
  both frontend and backend extension points
- Fix diagram alignment issues (outer box right border, stem connectors)
- Add ┬ connectors on lower box tops for arrow connections
- Fix sidebar_position conflict with dynamic-context.md
- Use only ASCII in code block diagrams for consistent alignment
@cocowh cocowh force-pushed the weagent/kb-permission-extension branch from 7a43fa6 to 5f59ae5 Compare April 30, 2026 03:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant